package top.chaol.t55557reader;

import android.os.Build;
import android.util.Log;

import java.util.HashMap;
/**
 * Created by chaol on 2019/6/14.
 */
public class T55557Card {
    private ResponseStruct _responseStruct;
    private CmdStruct _cmdStruct;
    private final String TAG = T55557Card.class.getSimpleName();

    public T55557Card() {
        this._responseStruct = new ResponseStruct();
        this._cmdStruct = new CmdStruct();
        this.statusCodeMap = new HashMap<Byte, UsbSerial.ErrorCode>(){{
            put((byte)0x00, UsbSerial.ErrorCode.SUCCESS);
            put((byte)0x01, UsbSerial.ErrorCode.ERROR);
            put((byte)0x80, UsbSerial.ErrorCode.SUCCESS_CONFIG);
            put((byte)0x81, UsbSerial.ErrorCode.ERROR_WRITE_FAILED);
            put((byte)0x82, UsbSerial.ErrorCode.ERROR_READ_FAILED);
            put((byte)0x83, UsbSerial.ErrorCode.ERROR_CARD_NOT_EXIST);
            put((byte)0x84, UsbSerial.ErrorCode.ERROR_CARD_TYPE);
            put((byte)0x87, UsbSerial.ErrorCode.ERROR_KNOW);
            put((byte)0x85, UsbSerial.ErrorCode.ERROR_PARAM);
            put((byte)0x8f, UsbSerial.ErrorCode.ERROR_CMD_CODE);
        }};
    }

    //| 命 令 字 |         名称         |               描述               |
    //| :------: | :------------------: | :------------------------------: |
    //|          | 系统命令 (0x80~0x8F) |                                  |
    //|   0x84   |    Write_UserInfo    |           写卡数据信息           |
    //|   0x85   |    Read_UserInfo     |     读取卡中指定块的数据信息     |
    //|   0x88   |      Read-Page1      | 读卡1页的数据，即1页中的块1和块2 |
    //|   0x8A   |      Read-Page0      |     读卡0页的数据，即块1-块7     |
    public CmdStruct writeDataCmd(byte[] data) {
        this._cmdStruct.setCmd((byte)0x84);
        this._cmdStruct.setData(data);
        return this._cmdStruct;
    }
	
	public CmdStruct writeDataCmd(byte block, boolean readonly, boolean withPwd, byte[] pwd, byte[] data){
		byte[] _dat = new byte[11];
		_dat[0] = block;
		if(readonly)
			_dat[1]=(byte)0xaa;
		else
			_dat[1]=0x55;
		if(withPwd){
			_dat[2]=(byte)0xaa;
			//for(int i=0;i<4;i++){
			//	_dat[i+3] = pwd[i];
			//}
			System.arraycopy(pwd, 0, _dat, 3, 4);
		} else {
			_dat[2] = 0x55;
		}
		System.arraycopy(data, 0, _dat, 7, 4);
		this._cmdStruct.setCmd((byte)0x84);
		this._cmdStruct.setData(_dat);
		return this._cmdStruct;
	}

    public CmdStruct readDataCmd() {
        this._cmdStruct.setCmd((byte)0x85);
        return this._cmdStruct;
    }

    public CmdStruct readDataCmd(byte block, boolean withPwd, byte[] pwd) {
        byte[] _dat = new byte[6];
        this._cmdStruct.setCmd((byte)0x85);
        _dat[0] = block;
        if (withPwd) {
            _dat[1] = (byte)0xaa;
            System.arraycopy(pwd, 0, _dat, 2, 4);
        } else {
            _dat[1] = 0x55;
        }
        this._cmdStruct.setData(_dat);
        return this._cmdStruct;
    }

    public CmdStruct readPage1Cmd() {
        this._cmdStruct.setCmd((byte)0x88);
        return this._cmdStruct;
    }

    public CmdStruct readPage0Cmd() {
        this._cmdStruct.setCmd((byte)0x8A);
        return this._cmdStruct;
    }

    public ResponseStruct getResponseStruct() {
        return _responseStruct;
    }

    public CmdStruct getCmdStruct() {
        return _cmdStruct;
    }

    public ResponseStruct parseResponse(byte[] raw) {
        ResponseStruct _ret = this._responseStruct;
        byte[] _data;
        int _dataLength = 0;
        if (raw.length < 6) {
            _ret.setStatus((byte) 0x85);
        } else {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                _dataLength = Byte.toUnsignedInt(raw[2]) - 0x01;
            } else {
                int _t = raw[2];
                if (_t < 0)
                    _t += 256;
                _dataLength = _t - 1;
            }
            if (_dataLength + 5 <= raw.length) {
                if (raw[0] != (byte) 0xAA || raw[_dataLength + 5] != (byte) 0xBB) {
                    _ret.setStatus((byte) 0x85);
                    _dataLength = 0;
                } else {
                    // Success
                    _ret.setStatus(raw[3]);
                }
                //            _ret.computeBcc();

            } else {
                _ret.setStatus((byte) 0x85);
                _dataLength = 0;
            }
        }
        _data = new byte[_dataLength];
        System.arraycopy(raw, 4, _data, 0, _dataLength);
        _ret.setData(_data);
        if (_ret.getBcc() != raw[_dataLength + 4]) {
            _ret.setStatus((byte) 0x85);
            _ret.setData(new byte[0]);
        }
        Log.d(TAG, _ret.toHexString());
        Log.d(TAG, Integer.toHexString((int) raw[3] ));
        return _ret;
    }

    public UsbSerial.ErrorCode getResponseStatusMessage() {
        return this.statusCodeMap.get(this._responseStruct.getStatus());
    }

    public UsbSerial.ErrorCode getResponseStatusMessage(byte code) {
        return this.statusCodeMap.get(code);
    }

    //命令包格式 (主机到读写器)：
    //
    //| STX  | CARD_ID | DATA_LENGTH | CMD  | DATA[0..N] | BCC  | ETX  |
    //| ---- | ------- | ----------- | ---- | ---------- | ---- | ---- |
    //| 0xAA | 0x02    |             |      |            |      | 0xBB |
    //
    //(BCC) = CARD ID ⊕ DATALENGTH ⊕ CMD ⊕ DATA [0] ⊕ … ⊕ DATA [n], where ⊕ is the “EOR”.
    //
    //返回包格式 (读写器到主机)
    //
    //| STX  | CARD_ID | DATA_LENGTH | STATUS | DATA[0..N] | BCC  | ETX  |
    //| ---- | ------- | ----------- | ------ | ---------- | ---- | ---- |
    //| 0xAA | 0x02    |             |        |            |      | 0xBB |
    //
    // (BCC) = CARD ID ⊕ DATA LENGTH ⊕ STATUS ⊕ DATA [0] ⊕ … ⊕ DATA [n], where ⊕ is the “EOR”.
    private static class DataStruct {
        private byte stx;
        private byte cardId;
        private byte dataLength;
        private byte cmdOrStatus;
        private byte[] data;
        private byte bcc;
        private byte etx;

        public DataStruct() {
            this.stx = (byte)0xAA;
            this.cardId = (byte)0x02;
            this.etx = (byte)0xBB;
            this.cmdOrStatus = 0x0;
            this.dataLength = 1;
            this.data = new byte[0];
            this.bcc = 0;
        }

        public byte getBcc() {
            this.computeBcc();
            return bcc;
        }

        public String toHexString() {
            this.computeBcc();
            int _length = this.dataLength + 5;
            StringBuffer _retBuffer = new StringBuffer(_length);
            _retBuffer.append(Integer.toHexString(this.stx < 0? this.stx + 256: this.stx).length());
            _retBuffer.append(Integer.toHexString(this.cardId < 0? this.cardId + 256: this.cardId));
            _retBuffer.append(Integer.toHexString(this.dataLength < 0? this.dataLength + 256: this.dataLength));
            _retBuffer.append(Integer.toHexString(this.cmdOrStatus < 0? this.cmdOrStatus + 256: this.cmdOrStatus));
            for(int i=0, len=this.data.length; i<len; i++){
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    _retBuffer.append(Integer.toHexString(Byte.toUnsignedInt(this.data[i])));
                } else {
                    _retBuffer.append(Integer.toHexString(this.data[i] < 0? this.data[i] + 256: this.data[i]));
                }
            }
            _retBuffer.append(Integer.toHexString(this.bcc < 0? this.bcc + 256: this.bcc));
            _retBuffer.append(Integer.toHexString(this.etx < 0? this.etx + 256: this.etx));
            return _retBuffer.toString();
        }

        public byte[] toByteArray() {
            this.computeBcc();
            int _length = this.dataLength + 5;
            byte[] _ret = new byte[_length];
            _ret[0] = this.stx;
            _ret[1] = this.cardId;
            _ret[2] = this.dataLength;
            _ret[3] = this.cmdOrStatus;
            for(int i=0, len=this.data.length; i<len; i++){
                _ret[4+i] = this.data[i];
            }
            _ret[_length - 2] = this.bcc;
            _ret[_length - 1] = this.etx;
            return _ret;
        }

        public void computeBcc() {
            int _bcc = this.cardId ^ this.dataLength ^ this.cmdOrStatus;
            for(int i=0, len=this.data.length; i<len; i++)
                _bcc = _bcc^this.data[i];
            this.bcc = (byte)_bcc;
        }

        public byte getDataLength() {
            return dataLength;
        }

        public byte[] getData() {
            return data;
        }

        public void setData(byte[] data) {
            byte[] _dat = data;
            if (data.length > 256){
                _dat = new byte[256];
                int _offset = data.length - 256;
                System.arraycopy(data, _offset, _dat, 0, 256);
            }
            this.dataLength = (byte) (_dat.length + 1);
            this.data = _dat;
        }
    }

    public static class CmdStruct extends DataStruct {

        public CmdStruct(byte cmd) {
            super();
            super.cmdOrStatus = cmd;
        }

        public CmdStruct() {
            super();
        }

        public void setCmd(byte cmd) {
            super.cmdOrStatus = cmd;
        }
    }

    public static class ResponseStruct extends DataStruct {

        public ResponseStruct(byte status) {
            super();
            super.cmdOrStatus = status;
        }

        public ResponseStruct() {
            super();
        }

        public void setStatus(byte status) {
            super.cmdOrStatus = status;
        }

        public byte getStatus() {
            return super.cmdOrStatus;
        }
    }

    public HashMap<Byte, UsbSerial.ErrorCode> statusCodeMap;
}
